package com.application.service.Impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.application.controller.Result;
import com.application.dao.UserDao;
import com.application.dao.communityDao;
import com.application.domain.Leader;
import com.application.domain.RedisData;
import com.application.domain.User;
import com.application.domain.Community;
import com.application.service.CommunityService;
import com.application.service.LeaderService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.application.util.RedisConstant.COMMUNITY_KEY;
import static com.application.util.RedisConstant.LOCK_COMMUNITY_KEY;

@Service
public class CommunityServiceImpl extends ServiceImpl<communityDao, Community> implements CommunityService {
    @Autowired
    communityDao communityDao;
    @Autowired
    UserDao userDao;
    @Autowired
    LeaderService leaderService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;



    @Override
    @Transactional
    public int saveCommunity(Community community) {
        int insert = communityDao.insert(community);
        return insert;
    }

    @Override
    @Transactional
    public int deleteByName(String name) {
        // 这里的删除 也需要到  redis当中删除
        // 先删除数据库
        int deleteById = communityDao.deleteById(name);

        // 然后删除 redis
        stringRedisTemplate.delete(COMMUNITY_KEY + name);

        return deleteById;
    }

    @Override
    @Transactional
    public int deleteByNames(List<String> names) {
        int deleteBatchIds = communityDao.deleteBatchIds(names);
        return deleteBatchIds;
    }

    @Override
    @Transactional
    public int deleteAll() {
        int delete = communityDao.delete(null);
        return delete;
    }

    @Override
    @Transactional
    public int updateByName(Community Community, String openid) {

        int updateByOpenid = -1;

            User user = userDao.selectByOpenidUser(openid);
            int updateById=0;
            //  肯定是先判断  level是否足够  才可以对  community进行改变  从而才能对leader进行改变
            if(user.getLevel()>=2){
                LambdaQueryWrapper<com.application.domain.Community> lqw = new LambdaQueryWrapper<>();
                lqw.eq(com.application.domain.Community::getName,Community.getName());
                updateById = communityDao.update(Community,lqw);
                    if(Community.getName()!=null || Community.getTel()!=null){
                        Leader leader = new Leader(Community.getName(),Community.getTel(),openid);
                        leader.setVersion(null);
                        System.out.println(leader.getVersion());
                        leaderService.updateByOpenid(leader,openid);
                    }
            }
            return updateById;
    }

    @Override
    @Transactional
    public Community selectByName(String name) {
        Community community = communityDao.selectById(name);
        return community;
    }

    @Override
    @Transactional
    public List<Community> selectByNames(List<String> names) {
        List<Community> communities = communityDao.selectBatchIds(names);
        return communities;
    }


    // 这里就是  创建新线程 用来 更新redis的过期数据
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    @Override
    // 这里进行预热
    public List<Community> selectAll() {
        List<Community> communities = communityDao.selectList(null);
        for (Community community : communities) {
            Integer id = community.getId();
            RedisData redisData = new RedisData();
            redisData.setData(community);
            redisData.setExpireTime(LocalDateTime.now().plusSeconds(20L));
            stringRedisTemplate.opsForValue().set(COMMUNITY_KEY + id,JSONUtil.toJsonStr(redisData));
        }
        return communities;
    }

    // 这里我们使用  redis当中    先预热将 社团的信息  存放到 redis当中  然后 我们从redis中 进行选取
    // 为了 防止  缓存穿透  我们使用逻辑过期 即 永远不会过期  但是 需要手动判断
    // 而且 如果  redis中过期了 或者找不到了   那么 我们开启一个
    public Community queryById(Integer id){
        // 首先就是 根据  key 到 redis当中查找
        String key = COMMUNITY_KEY + id;
        // 我们使用 字符串来存放社团信息
        String result = stringRedisTemplate.opsForValue().get(key);
        // 如果店铺信息不存在redis当中  那么 我们直接返回null值 不去访问数据库了  因为 我们预先预热了数据到redis当中
        if(StrUtil.isBlank(result)){
            return null;
        }
        // 有数据  那么 我们需要进行转换类型  然后逻辑判断是否过期
        RedisData redisData = JSONUtil.toBean(result, RedisData.class);
        // 从RedisData获取数据
        // 因为 redisData中的data类型为object  所以我们先获取  JSONObject类型的 然后再转换
        JSONObject jsonObject = (JSONObject)redisData.getData();
        LocalDateTime expireTime = redisData.getExpireTime();
        // 转换成想要的类型
        Community community = JSONUtil.toBean(jsonObject, Community.class);

        // 判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            // 还没有过期
            return community;
        }

        // 过期了 那么 我们需要 开启一个线程 对社团信息进行更新  并且上锁
        String lock_key = LOCK_COMMUNITY_KEY + id;

        if(tryLock(lock_key)){
            System.err.println("过期了  我们需要更新");
            // 如果还是过期了  那么 我们就异步更新
            // 里面不要放 return 语句 因为 异步不知道return什么
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try{
                    // 这里多放个 second  可以 进行随机获取  不要 多个 热点社团同时过期
                    // 而且 要在当前时间加上时间  不然 expireTime一直是 当前时间 每次都会 更新
                    // 即使 我们使用 异步更新 但是 如果  多个社团同时过期  线程压力还是很大
                    savecommunity2Redis(id,10L);
                }catch (Exception e){
                    throw new RuntimeException(e);
                }finally {
                    unLock(lock_key);
                }
            });

            //  还是先到 redis当中 获取 信息  查看 是否真的过期  因为 获取锁成功有两种情况
            //  一个是  自己是第一个发现过期的  另一个是  刚好别的线程更新完  释放了  自己获得了
            String communityJson = stringRedisTemplate.opsForValue().get(key);
            RedisData redisData1 = JSONUtil.toBean(communityJson, RedisData.class);
            JSONObject jsonData = (JSONObject) redisData1.getData();
            Community community1 = JSONUtil.toBean(jsonData, Community.class);
            LocalDateTime expireTime1 = redisData1.getExpireTime();

            if(expireTime1.isAfter(LocalDateTime.now())){
                // 这里就是 不过期的数据
                return community1;
            }
        }
        // 返回 过期的社团信息
        return community;
    }

    @Override
    public Result updateCommunity(Community community) {
        // 先更新数据库  然后删除redis
        boolean isSuccess = updateById(community);
        stringRedisTemplate.delete(COMMUNITY_KEY + community.getId());
        String mes = isSuccess ? "更新成功" : "更新失败";
        Integer code = isSuccess ? 200 : 500;
        return new Result(code,null,mes);
    }

    private boolean tryLock(String  key){
        Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.MINUTES);
        // 使用工具类 防止 拆箱的 null
        return BooleanUtil.isTrue(isSuccess);
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }

    // 这里就是 逻辑过期之后 更新redis当中的社团数据
    public void savecommunity2Redis(Integer id,Long expireSecond){
        // 先到数据库拿 最新的数据
        Community community = getById(id);
        // 然后保存为 RedisData格式
        RedisData redisData = new RedisData();
        redisData.setData(community);
        // 而且 更新 逻辑过期时间
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSecond));

        // 写入 redis当中
        stringRedisTemplate.opsForValue().set(COMMUNITY_KEY + id,JSONUtil.toJsonStr(redisData));
    }


    @Override
    public int DownLoad(Community community) {
        int updateById = communityDao.updateById(community);
        return  updateById;
    }

    @Override
    public int applyForCommunity(String name, String username, String tel) {
        int applyForCommunity = communityDao.applyForCommunity(name, username, tel);
        return applyForCommunity;
    }

    @Override
    public List<Community> getBySchool(String school) {
        List<Community> schools = communityDao.getBySchool(school);
        return schools;
    }
}
